var gamejs = require('gamejs');
var utils = require('js/utils');
/**
 * MAP : an object representing the gamefield.
 *
 * Properties
 * - obstacles: a sprite.Group containing all obstacles on the map
 * - enemies: a sprite.Group containing all enemies on the map
 * - turrets: a sprite.Group containing all turrets on the map
 * - radars: a sprite.Group containing all radars on the map
 * - size: an array [width, height] with the size of the map
 * - currentMatch: the match where the map is used
 */

/**
 * Creates a map, which holds a matrix of the navigable terrain and the obstacles
 *
 * @param {Match} match the current match
 * @param {number} width the map width
 * @param {number} height the map height
 *
 * @constructor
 */
var Map = exports.Map = function(match, width , height) {
	// Initialize the 2-dimensional array
	this.matrix = new Array(width);
	for(var i = 0; i < width; i++) {
		this.matrix[i] = new Array(height);
		for(var j = 0; j < height; j++) {
			this.matrix[i][j] = true;
		}
	}
	this.obstacles = new gamejs.sprite.Group();
	this.enemies = new gamejs.sprite.Group();
	this.turrets = new gamejs.sprite.Group();
	this.radars = new gamejs.sprite.Group();
	this.projectiles = new gamejs.sprite.Group();
	this.width = width;
	this.height = height;
	this.currentMatch = match;
	this.background = gamejs.image.load(IMAGE_ROOT + "background.png");

	// Sprite Group saving all the GameEntity that fills space
	this._fillingEntities = new gamejs.sprite.Group();
};

/**
 * Return the match where this map work.
 *
 * @return {Match} The match where this map work.
 */
Map.prototype.getMatch= function() {
	return this.currentMatch;
};

/**
 * Get the current map size
 *
 * @returns {Number[]}  The width and height of the Surface
 */
Map.prototype.getSize = function() {
	return [this.width, this.height];
};

/**
 * Check if a given point is inside the map
 *
 * @param {Number[]} coordinates The coordinates as vector [x, y]
 *
 * @returns {boolean} true if the point is inside the map, false otherwise
 *
 * @throws {TypeError} If coordinates are not of the correct type ([number, number])
 *
 */
Map.prototype.isInRange = function(coordinates) {
	if ( (typeof coordinates[0] !== "number") ||
			(typeof coordinates[1] !== "number")) {
		throw new TypeError("Coordinates incorrect type");
	}
	return !((coordinates[0] < 0 || coordinates[0] >= this.width) ||
			(coordinates[1] < 0 || coordinates[1] >= this.height));
};

/**
 * Fill a pixel
 *
 * @param {array} coordinates The coordinates as vector [x, y]
 *
 * @throws {TypeError} If coordinates are not in the correct type
 *                     ([number, number])
 *
 * @throws {RangeError} If coordinates are out of range
 *                      ([ 0<=number<width, 0<=number<height])
 */
Map.prototype.fill = function(coordinates) {
	if(! this.isInRange(coordinates)){
		throw new RangeError("Coordinates out of range");
	}
	this.matrix[coordinates[0]][coordinates[1]] = false;
};

/**
 * Clear a pixel
 *
 * @param {array} coordinates The coordinates as vector [x, y]
 *
 * @throws {TypeError} If coordinates are not in the correct
 *                     type ([number, number])
 *
 * @throws {RangeError} If coordinates are out of range
 *                      ([ 0<=number<width, 0<=number<height])
 */
Map.prototype.clear = function(coordinates) {
	if(! this.isInRange(coordinates)){
		throw new RangeError("Coordinates out of range");
	}
	this.matrix[coordinates[0]][coordinates[1]] = true;
};

/**
 * Tells if the pixel is filled
 *
 * @param {array} coordinates The coordinates as vector [x, y]
 *
 * @throws {TypeError} If coordinates are not in the correct
 *                     type ([number, number])
 *
 * @throws {RangeError} If coordinates are out of range
 *                      ([ 0<=number<width, 0<=number<height])
 *
 * @returns {boolean} True if the pixel is filled, false otherwise
 */
Map.prototype.isFilled = function(coordinates) {
	if(! this.isInRange(coordinates)){
		throw new RangeError("Coordinates out of range");
	}
	
	return ( !this.matrix[coordinates[0]][coordinates[1]] );
};

/**
 * Fills the space of gameEntity if gameEntity.fillsMap() is true.
 *
 * This methods only fills the space occupied by the gameEntity: if you also
 * want to draw/update the entity you are about to add, you should instead
 * call ONLY the specific method (for example addTurret, addObstacle,
 * addRadar etc..)
 *
 * @param {gameEntity} gameEntity
 *
 * @throws {Error} if the space to be filled by the obstacle is already
 *                 filled by something else, or if when adding the entity
 *                 you are entirely blocking the enemies.
 */
Map.prototype.addGameEntity = function(gameEntity) {
	if ( ! gameEntity.fillsMap() ) {
		return;  // Nothing to do if a GameEntity doesn't fill the map
	}
	// Get all the entity points
	var entityPoints = gameEntity.pointsOnMap();
	// Check if the points are free
	for (var i = 0; i < entityPoints.length; i++) {
		if ( this.isFilled(entityPoints[i]) ) {
			throw new Error("I can't add the GameEntity. Map already filled.");
		}
	}
	// Fill all the points (here we are sure they are all cleared)
	entityPoints.forEach(Map.prototype.fill, this);

	// TODO
	// Check if when adding this GameEntity we block the enemies

	// Adds the gameEntity to the "private" group
	this._fillingEntities.add(gameEntity);
};

/**
 * Remove a GameEntity from the Map and clear the space previously occupied
 * (only if the gameEntity.fillsMap() is true)
 *
 * This methods only clears the space occupied by the gameEntity: if you also
 * want to stop drawing/updating it, you should instead call ONLY the
 * specific method (for example removeTurret, removeObstacle, etc..)
 *
 * @param {gameEntity} gameEntity
 *
 * @throws {Error} if this Map doesn't have the gameEntity
 */
Map.prototype.removeGameEntity = function(gameEntity) {
	// If gameEntity is in the Map remove it and clear the area previously
	// occupied
	if ( ! gameEntity.fillsMap()) {
		return;  // If the gameEntity doesn't fill the Map
	}
	if (this._fillingEntities.has(gameEntity)) {
		// Remove it
		this._fillingEntities.remove(gameEntity);
		// Get all the points...
		var entityPoints = gameEntity.pointsOnMap();
		// ...and clear them
		entityPoints.forEach(Map.prototype.clear, this);
	}
	// if gameEntity isn't in the Map raise an exception
	else {
		throw new Error("This Map doesn't have the passed gameEntity.");
	}
};

/**
 * Return the enemies group.
 *
 * @return {Group} The enemies group.
 */
Map.prototype.getEnemies=function() {
	return this.enemies;
};

/**
 * Adds an enemy to the map
 *
 * @param {Enemy} enemy The enemy to add
 */
Map.prototype.addEnemy = function(enemy) {
	this.enemies.add(enemy);
};

/**
 * Removes an enemy from the map
 *
 * @param {Enemy} enemy  They enemy to remove
 */
Map.prototype.removeEnemy = function(enemy) {
	this.enemies.remove(enemy);
};

/**
 * Return the obstaclesgroup.
 *
 * @return {Group} The obstacles group.
 */
Map.prototype.getObstacles=function() {
	return this.obstacles;
};

/**
 * Adds an obstacle to the map and fills the sprite area
 *
 * @param {Obstacle} obstacle The obstacle to add
 *
 * @throws {Error} if the space to be filled by the obstacle is already
 *                 filled by something else
 */
Map.prototype.addObstacle = function(obstacle) {
	this.addGameEntity(obstacle);
	this.obstacles.add(obstacle);
};

/**
 * Removes an obstacle from the map and clears the sprite area
 *
 * @param {Obstacle} obstacle The obstacle to remove
 *
 * @throws {Error} If this Map doesn't have the obstacle
 */
Map.prototype.removeObstacle = function(obstacle) {
	// If obstacle is in the Map remove it and clear the
	// area previously occupied
	if (this.obstacles.has(obstacle)) {
		this.obstacles.remove(obstacle);
		this.removeGameEntity(obstacle);
	}
	// if obstacle isn't in the Map raise an exception
	else {
		throw new Error("This Map doesn't have the passed obstacle.");
	}
};

/**
 * Return the radars group.
 *
 * @return {Group} The radars group.
 */
Map.prototype.getRadars= function() {
	return this.radars;
};

/**
 * Adds a radar to the map and fills the sprite area
 *
 * @param {Radar} radar The radar to add
 *
 * @throws {Error} if the space to be filled by the radar is already
 *                 filled by something else
 */
Map.prototype.addRadar = function(radar) {
	this.addGameEntity(radar);
	this.radars.add(radar);
};

/**
 * Removes a radar from the map and clears the sprite area
 *
 * @param {Radar} radar The radar to remove
 *
 * @throws {Error} If this Map doesn't have the radar
 */
Map.prototype.removeRadar = function(radar) {
	// If radar is in the Map remove it and clear the area previously occupied
	if (this.radars.has(radar)) {
		this.radars.remove(radar);
		this.removeGameEntity(radar);
	}
	// if radar isn't in the Map raise an exception
	else {
		throw new Error("This Map doesn't have the passed radar.");
	}
};

/**
 * Return the turrets group.
 *
 * @return {Group} The turrets group.
 */
Map.prototype.getTurrets= function() {
	return this.turrets;
};

/**
 * Adds a turret on the map filling the sprite area from the matrix
 *
 * @param {Turret} turret The turret to add on the map
 */
Map.prototype.addTurret = function(turret) {
	this.addGameEntity(turret);
	this.turrets.add(turret);
};

/**
 * Removes a turret from the map clearing the sprite area from the matrix
 *
 * @param {Turret} turret The turret to remove
 *
 * @throws {Error} If the map doesn't have the turret
 */
Map.prototype.removeTurret = function(turret){
	// If the turret is in the Map remove it and clear the area
	// previously occupied
	if (this.turrets.has(turret)) {
		// Remove it
		this.turrets.remove(turret);
		this.removeGameEntity(turret);
	}
	// if the turret isn't in the Map raise an exception
	else {
		throw new Error("This Map doesn't have the searched turret.");
	}
};

/**
 * Return the projectiles group.
 *
 * @return {Group} The projectiles group.
 */
Map.prototype.getProjectiles= function() {
	return this.projetiles;
};

/**
 * Adds a projectile on the map
 *
 * @param {Projectile} projectile The Projectile to add on the map
 */
Map.prototype.addProjectile = function(projectile) {
	this.projectiles.add(projectile);
};

/**
 * Removes a projectile from the map
 *
 * @param {Projectile} projectile The Projectile to remove
 *
 * @throws {Error} If the map doesn't have the projectile
 */
Map.prototype.removeProjectile = function(projectile){
	// If the projectile is in the Map remove it
	if (this.projetiles.has(projectile)) {
		this.projectiles.remove(projectile);
	}
	// if the projectile isn't in the Map raise an exception
	else {
		throw new Error("This Map doesn't have the searched projectile.");
	}
};

/**
 * Update function
 */
Map.prototype.update = function(msDuration) {
	this.radars.update(msDuration);
	this.enemies.update(msDuration);
	this.turrets.update(msDuration);
	this.projectiles.update(msDuration);
};

/**
 * Draw on surface
 
 * @param {gamejs.surface} where to draw
 */
Map.prototype.draw = function(surface) {
	surface.blit(this.background);
	this.radars.draw(surface);
	this.enemies.draw(surface);
	this.obstacles.draw(surface);
	this.turrets.draw(surface);
	this.projectiles.draw(surface);
};